#include <cassert>
#include "profile/regprofile.h"

regprofile::regprofile()
{
	hbase = 0;
}

regprofile::regprofile(HKEY hkey, string const & path, bool create, bool nothrow)
{
	hbase = 0;
	open(hkey, path, create, nothrow);
}

void regprofile::open(HKEY hkey, string const & path, bool create, bool nothrow)
{
	close();

	LONG r;
	if (create)
		r = RegCreateKeyEx(hkey, path.c_str(), 0, 0, 0, KEY_ALL_ACCESS, 0, &hbase, 0);
	else
		r = RegOpenKeyEx(hkey, path.c_str(), 0, KEY_ALL_ACCESS, &hbase);

	if (r == ERROR_SUCCESS)
		return;

	hbase = 0;
	if (!nothrow)
		classify_error(r, string("registry key '") + path + "' opening error");
}

void regprofile::close()
{
	if (hbase != 0) {
		RegCloseKey(hbase);
		hbase = 0;
	}
}

regprofile::~regprofile()
{
	close();
}

bool regprofile::query(string const & path) const
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		DWORD cbData = 0;
		return ERROR_SUCCESS == RegQueryValueEx(hbase, value_name.c_str(), 0, 0, 0, &cbData);
	}
	else {
		try {
			regprofile subprofile(hbase, key_name, false, true);
			if (!subprofile.is_open())
				return false;
			return subprofile.query(value_name);
		}
		catch (error const &) {
			return false;
		}
	}
}

void regprofile::classify_error(LONG r, string message)
{
	if (r == ERROR_SUCCESS)
		return;

	char * buf;
	if (FormatMessage(
		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, 
		r, 0, (LPTSTR)&buf, 0, 0)) 
	{
		if (!message.empty())
			message += ":\n";
		message += buf;
		LocalFree(buf);
	}

	switch (r) {
	case ERROR_ACCESS_DENIED:
		throw access_denied(message);
	case ERROR_PATH_NOT_FOUND:
	case ERROR_FILE_NOT_FOUND:
		throw not_found(message);
	default:
		throw error(message);
	}
}

void regprofile::split_path(string const & path, string & key_name, string & value_name)
{
	string::size_type n = path.find_last_of('\\');
	if (n == string::npos) {
		key_name.clear();
		value_name = path;
	}
	else if (n == path.size()-1) {
		key_name = path; //including last backslash
		value_name.clear();
	}
	else {
		key_name = path.substr(0, n+1); //including last backslash
		value_name = path.substr(n+1);
	}
}

void regprofile::get_regvalue_meta(string const & value_name, DWORD & cbData, DWORD & type) const
{
	LONG r = RegQueryValueEx(hbase, value_name.c_str(), 0, &type, 0, &cbData);
	if (r != ERROR_SUCCESS)
		classify_error(r, string("querying size and type of registry value '") + value_name + "' error");
}

void regprofile::get_regvalue(string const & value_name, DWORD & cbData, void * data) const
{
	LONG r = RegQueryValueEx(hbase, value_name.c_str(), 0, 0, (LPBYTE)data, &cbData);
	if (r != ERROR_SUCCESS)
		classify_error(r, string("querying registry value '") + value_name + "' error");
}

void regprofile::get_string(string const & path, string & value) const
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		DWORD cbData = 0, type;
		get_regvalue_meta(value_name, cbData, type);
		if (type != REG_SZ)
			throw incompatible_type(string("registry value '") + value_name + "' is not string");
		value.resize(cbData);
		get_regvalue(value_name, cbData, &*value.begin());
		value.resize(cbData-1);
	}
	else {
		regprofile subprofile(hbase, key_name, false);
		subprofile.get_string(value_name, value);
	}
}

void regprofile::get_blob(string const & path, reg_blob & value) const
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		DWORD cbData = 0, type;
		get_regvalue_meta(value_name, cbData, type);
		if (type != REG_BINARY)
			throw incompatible_type(string("registry value '") + value_name + "' is not binary");
		value.resize(cbData);
		get_regvalue(value_name, cbData, &*value.begin());
	}
	else {
		regprofile subprofile(hbase, key_name, false);
		subprofile.get_blob(value_name, value);
	}
}

void regprofile::get_llint(string const & path, long long & value) const
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		DWORD cbData = 0, type;
		get_regvalue_meta(value_name, cbData, type);
		switch (type) {
		case REG_DWORD: {
			int d;
			get_regvalue(value_name, cbData, &d);
			value = d;
			return;
		}
		case REG_QWORD: {
			get_regvalue(value_name, cbData, &value);
			return;
		}
		default:
			throw incompatible_type(string("Registry value '") + value_name + "' is not 32- or 64-bit integer");
		}
	}
	else {
		regprofile subprofile(hbase, key_name, false);
		subprofile.get_llint(value_name, value);
	}
}

void regprofile::get_double(string const & path, double & value) const
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		DWORD cbData = 0, type;
		get_regvalue_meta(value_name, cbData, type);
		switch (type) {
		case REG_DWORD:
		case REG_QWORD: {
			long long ll;
			get(path, ll);
			value = ll;
			return;
		}
		case REG_SZ: {
			if ((std::istringstream(get<std::string>(path)) >> value).fail())
				throw parse_error(string("Registry value '") + path + "' could not be converted to real type");
			return;
		}
		default:
			throw incompatible_type(string("Registry value '") + value_name + "' could not be converted to real type");
		}
	}
	else {
		regprofile subprofile(hbase, key_name, false);
		subprofile.get_double(value_name, value);
	}
}

void regprofile::set_string(string const & path, string const & value)
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		LONG r = RegSetValueEx(hbase, value_name.c_str(), 0, REG_SZ, (const BYTE *)value.c_str(), value.size()+1);
		if (r != ERROR_SUCCESS)
			classify_error(r, string("setting registry value '") + value_name + "' error");
	}
	else {
		regprofile subprofile(hbase, key_name);
		subprofile.set_string(value_name, value);
	}
}

void regprofile::set_blob(string const & path, reg_blob const & value)
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		LONG r = RegSetValueEx(hbase, value_name.c_str(), 0, REG_BINARY, (const BYTE *)&value[0], value.size());
		if (r != ERROR_SUCCESS)
			classify_error(r, string("setting registry value '") + value_name + "' error");
	}
	else {
		regprofile subprofile(hbase, key_name);
		subprofile.set_blob(value_name, value);
	}
}

void regprofile::set_int(string const & path, int value)
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		LONG r = RegSetValueEx(hbase, value_name.c_str(), 0, REG_DWORD, (const BYTE *)&value, sizeof(value));
		if (r != ERROR_SUCCESS)
			classify_error(r, string("setting registry value '") + value_name + "' error");
	}
	else {
		regprofile subprofile(hbase, key_name);
		subprofile.set_int(value_name, value);
	}
}

void regprofile::set_llint(string const & path, long long const & value)
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		LONG r = RegSetValueEx(hbase, value_name.c_str(), 0, REG_QWORD, (const BYTE *)&value, sizeof(value));
		if (r != ERROR_SUCCESS)
			classify_error(r, string("setting registry value '") + value_name + "' error");
	}
	else {
		regprofile subprofile(hbase, key_name);
		subprofile.set_llint(value_name, value);
	}
}

void regprofile::remove(string const & path)
{
	assert(is_open());
	string key_name, value_name;
	split_path(path, key_name, value_name);
	if (key_name.empty()) {
		LONG r = RegDeleteValue(hbase, value_name.c_str());
		if (r != ERROR_SUCCESS)
			classify_error(r, string("deleting registry value '") + value_name + "' error");
	}
	else {
		regprofile subprofile(hbase, key_name, false);
		subprofile.remove(value_name);
	}
}
